Optimal (optimized) simple rules ================================= An *optimal simple rule* (OSR) keeps the model as written -- including a simple, parametric feedback rule such as a Taylor rule -- and chooses the **rule coefficients** that minimize a policy loss function. Unlike fully optimal policy (see :doc:`Optimal policy`), the model still has as many equations as endogenous variables: no equation is dropped, only some of its parameters are picked optimally. .. contents:: :local: :depth: 2 The loss function ------------------ The loss is supplied to ``optimal_simple_rule`` at solve time, as a string in terms of the model's variables and parameters (it is the *planner's objective*, i.e. RISE *maximises* it -- write a welfare measure, or minus a quadratic loss). A discount factor can be attached; it does not affect the ranking of *unconditional* losses, so it is often left at the default ``0.99``:: % minus a weighted quadratic loss in inflation, the output % gap and the change in the policy rate Loss = '-.5*(1*PAI^2 + .3*Y^2 + 0.9*DI^2)'; % with an explicit discount factor: Loss = {0.99, '-.5*(1*PAI^2 + .3*Y^2 + 0.9*DI^2)'}; .. important:: The OSR objective is **always** supplied to ``optimal_simple_rule`` at solve time. It cannot be declared inside the model file: an ``@optimization_problem`` block in the ``.rs`` file is reserved strictly for *optimal policy* (the planner's first-order conditions replace policy equations), which is a different solve concept from OSR (the policy equations are kept and only their coefficients are chosen optimally). See :doc:`Optimal policy` for the model-file block; here the loss is just a string argument. The rule coefficients to optimize are listed the way estimated parameters are -- through a ``priors`` structure giving each one a support (start value, lower bound, upper bound):: priors = struct(); priors.gam_lag = {0.60, 0.00, 1}; % {start, lb, ub} priors.gam_y = {0.50, 0.00, 10}; (Approximate) analytical loss ------------------------------ If the third argument is empty, ``optimal_simple_rule`` minimises the **theoretical (unconditional)** loss -- computed from the model's (perturbation-implied) second moments, with no simulation:: m = dsge_model('Canonical_osr'); m = set(m, parameters = p); mest = optimal_simple_rule(m, Loss, [], estim_priors = priors); print_estimation_results(mest) Simulated loss --------------- Passing a shocks database (or a struct / function handle producing one) as the third argument makes ``optimal_simple_rule`` minimize the **conditional** (simulated) loss along those shock paths instead -- useful for nonlinear models or when you care about a specific scenario:: mest = optimal_simple_rule(m, Loss, shocks_db, estim_priors = priors); You can evaluate the loss of any parameterization directly with ``calculate_loss(m, Loss)`` (unconditional) or ``calculate_loss(m, Loss, shocks_db)`` (conditional) -- handy for comparing a hand-chosen rule with the optimized one. OSR and indirect inference --------------------------- Because OSR is solved exactly like an estimation problem -- an objective minimized over a set of parameters with bounds -- it composes with RISE's estimation machinery: the same optimizers (see :doc:`../../Estimation/Main Estimation`) are available, and OSR can be combined with indirect inference (choosing rule coefficients to match empirical moments or impulse responses) by supplying the corresponding objective. OSR with a user-defined objective ---------------------------------- ``optimal_simple_rule`` takes the loss as an *expression* -- the string (or ``{discount, string}`` cell) described above; it does not accept a function handle. If the objective you want cannot be written that way -- for example matching empirical moments or impulse responses -- use indirect inference directly, treating the rule coefficients as the estimated parameters. There the objective *is* a function handle, with the signature:: [critmin, retcode] = myobjective(m) where ``m`` is the solved model object, ``critmin`` is the scalar criterion to be minimized, and ``retcode`` is ``0`` when the evaluation succeeded. Policy frontiers ----------------- To trace a **policy frontier** -- how the model's volatilities trade off as a preference parameter (a weight in the loss, the persistence of the rule, ...) is varied -- use ``frontier``:: % vary the weight on the output gap over a 50-point grid % between 0 and 2 f = frontier(mest, 'lamb_y', [0, 2]); % or pass an explicit grid f = frontier(mest, 'lamb_y', linspace(0, 2, 21)); % use simulated rather than theoretical moments f = frontier(mest, 'lamb_y', [0, 2], true); ``f`` is a structure of the standard deviations of every model variable at each grid point (plus a ``stats__`` sub-structure with the grid, the number of simulation periods, and a per-point return code); plot the columns of interest against each other to get the frontier.